#include <stdlib.h>
#include "graphics/opengl.h"
#include "fluid_system.h"

namespace venus {
FluidSystem::FluidSystem(CGcontext context, int width, int height, int depth)
  : cg_context_(context), width_(width), height_(height), depth_(depth),
    current_state_idx_(0) {
  // we need 3 buffers - current, previous, and the state before that
  states_[0] = new VolumeBuffer(GL_RGBA16F_ARB, width, height, depth, 1);
  states_[1] = new VolumeBuffer(GL_RGBA16F_ARB, width, height, depth, 1);
  prev_state_ = new VolumeBuffer(GL_RGBA16F_ARB, width, height, depth, 1);

  // load programs
  clear_prog_ = new ProgramCG(cg_context_, "shaders/simulate.cg",
                              "clear", CG_GL_FRAGMENT);
  copy_prog_ = new ProgramCG(cg_context_, "shaders/simulate.cg",
                             "copy", CG_GL_FRAGMENT);
  splat_prog_ = new ProgramCG(cg_context_, "shaders/simulate.cg",
                              "splat", CG_GL_FRAGMENT);
  step_prog_ = new ProgramCG(cg_context_, "shaders/simulate.cg", 
                             "waves", CG_GL_FRAGMENT);

  voxel_size_ = step_prog_->GetParameter("voxelSize");
  cgGLSetParameter3f(voxel_size_, 1.f / width, 1.f / height, 1.f / depth);
  damping_ = step_prog_->GetParameter("damping");
  cgGLSetParameter1f(damping_, 0.99f);
  clear_color_ = clear_prog_->GetParameter("color");
  centre_ = splat_prog_->GetParameter("centre");
  radius_ = splat_prog_->GetParameter("radius");
  splat_color_ = splat_prog_->GetParameter("color");
  tex_ = copy_prog_->GetParameter("tex");
  tex_unit_ = cgGetParameterResource(tex_) - CG_TEXUNIT0;
  prev_tex_ = step_prog_->GetParameter("prevTex");
  prev_tex_unit_ = cgGetParameterResource(prev_tex_) - CG_TEXUNIT0;
  current_tex_ = step_prog_->GetParameter("currentTex");
  current_tex_unit_ = cgGetParameterResource(current_tex_) - CG_TEXUNIT0;
  Reset();
}

FluidSystem::~FluidSystem() {
  delete states_[0];
  delete states_[1];
  delete prev_state_;
  
  delete splat_prog_;
  delete step_prog_;
}

inline float FluidSystem::frand() {
  return rand() / (float) RAND_MAX;
}

// add a random splat to the simulation
void FluidSystem::Splat() {
  cgGLSetParameter3f(centre_, frand(), frand(), frand());
  cgGLSetParameter1f(radius_, 0.1f);
  cgGLSetParameter4f(splat_color_, frand(), frand(), frand(), 1.0f);

  states_[current_state_idx_]->set_blend_mode(VolumeBuffer::BLEND_ADDITIVE);
  states_[current_state_idx_]->RunProgram(splat_prog_);
  states_[current_state_idx_]->set_blend_mode(VolumeBuffer::BLEND_NONE);
}

// reset the simulation by clearing buffers
void FluidSystem::Reset() {
  cgGLSetParameter4f(clear_color_, 0.f, 0.f, 0.f, 0.f);
  states_[current_state_idx_]->RunProgram(clear_prog_);
  states_[1 - current_state_idx_]->RunProgram(clear_prog_);
  prev_state_->RunProgram(clear_prog_, 0);
}

// step the simulation
void FluidSystem::Step(float timestep) {
  // copy previous state
  glActiveTextureARB(GL_TEXTURE0_ARB + tex_unit_);
  glBindTexture(GL_TEXTURE_3D, states_[1 - current_state_idx_]->texture());
  prev_state_->RunProgram(copy_prog_);   

  // compute new state
  glActiveTextureARB(GL_TEXTURE0_ARB + current_tex_unit_);
  glBindTexture(GL_TEXTURE_3D, states_[current_state_idx_]->texture());
  glActiveTextureARB(GL_TEXTURE0_ARB + prev_tex_unit_);
  glBindTexture(GL_TEXTURE_3D, prev_state_->texture());
  states_[1 - current_state_idx_]->RunProgram(step_prog_);

  current_state_idx_ = 1 - current_state_idx_;
}

} // namespace venus
